/** * <copyright> * * Copyright (c) 2005, 2006, 2007, 2008 Springsite BV (The Netherlands) and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Martin Taal * </copyright> * * $Id: PersistenceMappingSchemaGenerator.java,v 1.4 2008/06/29 14:49:50 mtaal Exp $ */ package org.eclipse.emf.teneo.annotations.xml; import java.io.FileWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.emf.ecore.EAnnotation; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EEnum; import org.eclipse.emf.ecore.EEnumLiteral; import org.eclipse.emf.ecore.EModelElement; import org.eclipse.emf.ecore.ENamedElement; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.teneo.annotations.StoreAnnotationsException; import org.eclipse.emf.teneo.annotations.pamodel.PamodelPackage; import org.eclipse.emf.teneo.annotations.pannotation.PannotationPackage; import org.eclipse.emf.teneo.simpledom.Attribute; import org.eclipse.emf.teneo.simpledom.Document; import org.eclipse.emf.teneo.simpledom.Element; /** * Parses the pamodel and pannotation model to generate a xsd. * * @author <a href="mailto:mtaal@elver.org">Martin Taal</a> * @version $Revision: 1.4 $ */ public class PersistenceMappingSchemaGenerator { /** The source name used to set to which estructural feature a tag belongs */ public static final String ESTRUCTURAL_FEATURE_SOURCE_NAME = "teneo/internal/EStructuralFeatureName"; /** Used to set efeatures in the ecore model to be ignored */ public static final String PERSISTENCE_MAPPING_SOURCE = "teneo/internal/PersistenceMapping"; /** Is set on efeatures to denote that they are not supported */ private static final String UNSUPPORTED_SOURCE = "teneo/internal/Unsupported"; public static final String XML_SCHEMA_NAMESPACE = "http://www.w3.org/2001/XMLSchema"; /** The main method, ugly but effective */ public static void main(String[] args) { final PersistenceMappingSchemaGenerator pmsg = new PersistenceMappingSchemaGenerator(); pmsg.setAnnotationEPackages(new EPackage[] { PannotationPackage.eINSTANCE }); pmsg.setModelEPackage(PamodelPackage.eINSTANCE); try { final FileWriter fw = new FileWriter("/home/mtaal/mytmp/persistence-mapping.xsd"); fw.write(pmsg.generate()); fw.close(); } catch (Exception e) { throw new StoreAnnotationsException("Exception while generating mapping.xsd", e); } } /** The pamodel */ private EPackage modelEPackage; /** And the annotations packages */ private EPackage[] annotationEPackages; /** Mapping from ecore simple types to schema simple types */ private final Map<String, String> schemaTypeNamesByAnnotationType = new HashMap<String, String>(); /** Target name space */ private String nameSpace = "http://www.eclipse.org/emft/teneo"; /** Initialize some main things */ private void initialize() { schemaTypeNamesByAnnotationType.put("EBoolean", "xsd:boolean"); schemaTypeNamesByAnnotationType.put("EInt", "xsd:int"); schemaTypeNamesByAnnotationType.put("ELong", "xsd:long"); schemaTypeNamesByAnnotationType.put("EString", "xsd:string"); } /** Generate into a string */ public String generate() { initialize(); final Document doc = new Document(); // The root Element final Element root = new Element("xsd:schema").addAttribute("targetNamespace", nameSpace).addAttribute("elementFormDefault", "qualified").addAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema").addAttribute("xmlns", nameSpace); root.addElement("xsd:element").addAttribute("name", "persistence-mapping").addAttribute("type", "PersistenceMapping"); root.addElement("xsd:complexType").addAttribute("name", "PersistenceMapping").addElement("xsd:sequence") .addAttribute("minOccurs", "1").addAttribute("maxOccurs", "unbounded").addElement("xsd:element") .addAttribute("name", "epackage").addAttribute("type", "EPackage"); // first determine which types have only one string field, these are handled // slightly different because this makes the xml easier for (EPackage annotationEPackage : annotationEPackages) { final List<EClassifier> eclassifiers = new ArrayList<EClassifier>(annotationEPackage.getEClassifiers()); for (EClassifier eClassifier : eclassifiers) { String schemaTypeName = eClassifier.getName(); if (eClassifier instanceof EClass) { EClass eClass = (EClass) eClassifier; // Annotation types with a single feature are converted to simple type // references in the schema. if (oneMappableFeature(eClass)) { final EStructuralFeature eStructuralFeature = eClass.getEStructuralFeatures().get(0); final EClassifier eType = eStructuralFeature.getEType(); schemaTypeName = schemaTypeNamesByAnnotationType.get(eType.getName()); if (schemaTypeName == null) { schemaTypeName = eType.getName(); } schemaTypeNamesByAnnotationType.put(eClassifier.getName(), schemaTypeName); continue; } } schemaTypeNamesByAnnotationType.put(eClassifier.getName(), schemaTypeName); } } // process the annotations first to get the correct typing final List<Element> annotationList = new ArrayList<Element>(); for (EPackage annotationEPackage : annotationEPackages) { annotationList.addAll(processAnnotationEPackage(annotationEPackage)); } root.addElement(createEPackageElement()); root.addElement(createEClassElement()); root.addElement(createEAttributeElement()); root.addElement(createEReferenceElement()); root.addElement(createPropertyElement()); root.addElement(createEDataTypeElement()); root.getChildren().addAll(annotationList); doc.setRoot(root); return doc.emitXML(); } /** process annotation packages */ private List<Element> processAnnotationEPackage(EPackage epackage) { final ArrayList<Element> elemList = new ArrayList<Element>(); final List<EClassifier> eclassifiers = new ArrayList<EClassifier>(epackage.getEClassifiers()); Collections.sort(eclassifiers, new ENamedElementComparator()); for (EClassifier eClassifier : eclassifiers) { if (isIgnorable(eClassifier) || isUnsupported(eClassifier)) { continue; } String schemaTypeName = eClassifier.getName(); if (eClassifier instanceof EClass) { final EClass eClass = (EClass) eClassifier; if (eClass.isAbstract()) { continue; } final List<EStructuralFeature> eStructuralFeatures = eClass.getEAllStructuralFeatures(); if (eStructuralFeatures.isEmpty()) { continue; } // Annotation types with a single feature are converted to simple type references in // the schema. if (oneMappableFeature(eClass)) { /* * final EStructuralFeature eStructuralFeature = (EStructuralFeature) * eClass.getEStructuralFeatures().get(0); final EClassifier eType = * eStructuralFeature.getEType(); schemaTypeName = (String) * schemaTypeNamesByAnnotationType.get(eType.getName()); if (schemaTypeName == * null) { schemaTypeName = eType.getName(); } * schemaTypeNamesByAnnotationType.put(eClassifier.getName(), schemaTypeName); */ continue; } final Element complexTypeElement = createSchemaComplexType(eClass.getName()); elemList.add(complexTypeElement); final Element choiceElement = complexTypeElement.addElement("xsd:choice"); addZeroUnbounded(choiceElement); processStructuralFeatures(choiceElement, eStructuralFeatures); if (choiceElement.getChildren().size() == 0) { complexTypeElement.getChildren().remove(choiceElement); } } else if (eClassifier instanceof EEnum) { elemList.add(processEnum((EEnum) eClassifier)); } else { throw new RuntimeException("Unexpected EClassifier: " + eClassifier.eClass().getName()); } schemaTypeNamesByAnnotationType.put(eClassifier.getName(), schemaTypeName); } return elemList; } /** If more than one mappable structuralfeature */ private boolean oneMappableFeature(EClass eclass) { int cnt = 0; for (EStructuralFeature ef : eclass.getEStructuralFeatures()) { if (!isIgnorable(ef) && !isUnsupported(ef)) { cnt++; } } return cnt == 1; } /** Process an enum */ private Element processEnum(EEnum eEnum) { final Element simpleTypeElement = createSchemaSimpleType(eEnum.getName(), null); final Element restrictionElement = new Element("xsd:restriction"); restrictionElement.addAttribute(new Attribute("base", "xsd:token")); simpleTypeElement.addElement(restrictionElement); final List<EEnumLiteral> literals = eEnum.getELiterals(); for (EEnumLiteral literal : literals) { final Element enumerationElement = new Element("xsd:enumeration"); restrictionElement.addElement(enumerationElement); enumerationElement.addAttribute(new Attribute("value", literal.getLiteral())); } return simpleTypeElement; } /** Do the epackage */ private Element createEPackageElement() { final Element epackElement = new Element("xsd:complexType").addAttribute("name", "EPackage"); final Element choiceElement = epackElement.addElement("xsd:choice"); addZeroUnbounded(choiceElement); processStructuralFeatures(choiceElement, getPAnnotatedEPackage().getEAllStructuralFeatures()); choiceElement.addElement("xsd:element").addAttribute("name", "eclass").addAttribute("type", "EClass"); choiceElement.addElement("xsd:element").addAttribute("name", "edatatype").addAttribute("type", "EDataType"); // add the namespace-uri attribute epackElement.addElement("xsd:attribute").addAttribute("name", "namespace-uri").addAttribute("type", "xsd:anyURI").addAttribute("use", "required"); return epackElement; } /** Do the eClass */ private Element createEClassElement() { final Element eclassElement = new Element("xsd:complexType").addAttribute("name", "EClass"); final Element choiceElement = eclassElement.addElement("xsd:choice"); addZeroUnbounded(choiceElement); processStructuralFeatures(choiceElement, getPAnnotatedEClass().getEAllStructuralFeatures()); choiceElement.addElement("xsd:element").addAttribute("name", "eattribute").addAttribute("type", "EAttribute"); choiceElement.addElement("xsd:element").addAttribute("name", "ereference").addAttribute("type", "EReference"); choiceElement.addElement("xsd:element").addAttribute("name", "property").addAttribute("type", "Property"); choiceElement.addElement("xsd:element").addAttribute("name", "edatatype").addAttribute("type", "EDataType"); eclassElement.addElement("xsd:attribute").addAttribute("name", "name").addAttribute("type", "xsd:token") .addAttribute("use", "required"); return eclassElement; } /** Do the eReference element */ private Element createEReferenceElement() { final Element erefElement = new Element("xsd:complexType").addAttribute("name", "EReference"); final Element choiceElement = erefElement.addElement("xsd:choice"); addZeroUnbounded(choiceElement); processStructuralFeatures(choiceElement, getPAnnotatedEReference().getEAllStructuralFeatures()); erefElement.addElement("xsd:attribute").addAttribute("name", "name").addAttribute("type", "xsd:token") .addAttribute("use", "required"); return erefElement; } /** Do the eAttribute */ private Element createEAttributeElement() { final Element eattrElement = new Element("xsd:complexType").addAttribute("name", "EAttribute"); final Element choiceElement = eattrElement.addElement("xsd:choice"); addZeroUnbounded(choiceElement); processStructuralFeatures(choiceElement, getPAnnotatedEAttribute().getEAllStructuralFeatures()); eattrElement.addElement("xsd:attribute").addAttribute("name", "name").addAttribute("type", "xsd:token") .addAttribute("use", "required"); return eattrElement; } /** Do the eDataType */ private Element createEDataTypeElement() { final Element eattrElement = new Element("xsd:complexType").addAttribute("name", "EDataType"); final Element choiceElement = eattrElement.addElement("xsd:choice"); addZeroUnbounded(choiceElement); processStructuralFeatures(choiceElement, getPAnnotatedEDataType().getEAllStructuralFeatures()); eattrElement.addElement("xsd:attribute").addAttribute("name", "name").addAttribute("type", "xsd:token") .addAttribute("use", "required"); return eattrElement; } /** Do the property (comb. of ereference and eattribute */ private Element createPropertyElement() { final Element propertyElement = new Element("xsd:complexType").addAttribute("name", "Property"); final Element choiceElement = propertyElement.addElement("xsd:choice"); addZeroUnbounded(choiceElement); final List<EStructuralFeature> features = new ArrayList<EStructuralFeature>(getPAnnotatedEAttribute().getEAllStructuralFeatures()); features.removeAll(getPAnnotatedEReference().getEAllStructuralFeatures()); features.addAll(getPAnnotatedEReference().getEAllStructuralFeatures()); processStructuralFeatures(choiceElement, features); propertyElement.addElement("xsd:attribute").addAttribute("name", "name").addAttribute("type", "xsd:token") .addAttribute("use", "required"); return propertyElement; } /** Walk through a pamodel type and add references to each type to the passed element */ private void processStructuralFeatures(Element mainElement, List<EStructuralFeature> eStructuralFeatures) { final List<EStructuralFeature> eFeatures = new ArrayList<EStructuralFeature>(eStructuralFeatures); Collections.sort(eFeatures, new ENamedElementComparator()); for (EStructuralFeature ef : eFeatures) { processStructuralFeature(mainElement, ef); } } /** * Processes the EStructuralFeatures of a Pamodel EClass. */ private void processStructuralFeature(Element parentElement, EStructuralFeature eStructuralFeature) { final EClassifier eType = eStructuralFeature.getEType(); if (isIgnorable(eStructuralFeature) || isIgnorable(eType) || isUnsupported(eType)) { return; } final int minOccurs = (eStructuralFeature.isRequired() ? 1 : 0); // Determine the element name. final EAnnotation eAnnotation = eStructuralFeature.getEAnnotation(PERSISTENCE_MAPPING_SOURCE); String elementName = null; if (eAnnotation != null) { elementName = eAnnotation.getDetails().get("elementName"); } if (elementName == null) { // No explicit XML element name specified, so derive from the name instead. elementName = eStructuralFeature.getName(); if (eStructuralFeature.isMany() && elementName.endsWith("s")) { elementName = elementName.substring(0, elementName.length() - 1); } } // check for double occurences, can occur when doing the property tag // which combines ereference and eattribute features final String xmlName = convertToXmlName(elementName); for (Element otherElem : parentElement.getChildren()) { String name; if ((name = otherElem.getAttributeValue("name")) != null && name.compareTo(xmlName) == 0) { return; } } if (parentElement.element(convertToXmlName(elementName)) != null) { return; } String typeName = schemaTypeNamesByAnnotationType.get(eType.getName()); if (typeName == null) { typeName = eType.getName(); } if (eStructuralFeature instanceof EReference) { // EReferences are represented by child elements. final Element element = createSchemaElement(elementName, typeName, eStructuralFeature.getName()); if (parentElement.getName().compareTo("xsd:choice") != 0) { element.addAttribute(new Attribute("minOccurs", String.valueOf(minOccurs))); if (eStructuralFeature.isMany()) { element.addAttribute(new Attribute("maxOccurs", "unbounded")); } } parentElement.addElement(element); } else { // EAttributes are represented by attributes and optional child elements in case of many // multiplicity. final Element attributeElement = createSchemaAttribute(eStructuralFeature.getName(), typeName, eStructuralFeature.getName()); attributeElement.addAttribute(new Attribute("use", (minOccurs == 0 ? "optional" : "required"))); parentElement.getParent().addElement(attributeElement); if (eStructuralFeature.isMany()) { final Element element = createSchemaElement(eStructuralFeature.getName(), typeName, eStructuralFeature.getName()); parentElement.addElement(element); if (parentElement.getName().compareTo("xsd:choice") != 0) { element.addAttribute(new Attribute("minOccurs", "0")); element.addAttribute(new Attribute("maxOccurs", "unbounded")); } } } } /** Return the PAnnotatedEClass */ protected EClass getPAnnotatedEPackage() { return (EClass) modelEPackage.getEClassifier("PAnnotatedEPackage"); } /** Return the PAnnotatedEClass */ protected EClass getPAnnotatedEClass() { return (EClass) modelEPackage.getEClassifier("PAnnotatedEClass"); } /** Return the PAnnotatedEReference */ protected EClass getPAnnotatedEReference() { return (EClass) modelEPackage.getEClassifier("PAnnotatedEReference"); } /** Return the PAnnotatedEAttribute */ protected EClass getPAnnotatedEAttribute() { return (EClass) modelEPackage.getEClassifier("PAnnotatedEAttribute"); } /** Return the PAnnotatedEDataType */ protected EClass getPAnnotatedEDataType() { return (EClass) modelEPackage.getEClassifier("PAnnotatedEDataType"); } /** * Tests whether an EModelElement can be ignored for persistence mapping. * */ protected static boolean isIgnorable(EModelElement eModelElement) { final EAnnotation eAnnotation = eModelElement.getEAnnotation(PERSISTENCE_MAPPING_SOURCE); boolean ignore = false; if (eAnnotation != null) { ignore = Boolean.valueOf(eAnnotation.getDetails().get("ignore")).booleanValue(); } return ignore; } /** * Creates an XML Schema element. (<xsd:element>) */ private Element createSchemaElement(String name, String type, String eStructuralFeatureName) { final Element element = new Element("xsd:element"); element.addAttribute(new Attribute("name", convertToXmlName(name))); element.addAttribute(new Attribute("type", type)); if (!name.equals(eStructuralFeatureName)) { addAppInfoElement(element, eStructuralFeatureName); } return element; } /** * Creates an XML Schema complex type. (<xsd:complexType>) */ private Element createSchemaSimpleType(String name, String type) { final Element element = new Element("xsd:simpleType"); element.addAttribute(new Attribute("name", name)); if (type != null) { element.addAttribute(new Attribute("type", type)); } return element; } /** * Creates an XML Schema attribute element. (<xsd:attribute>) */ private Element createSchemaAttribute(String name, String type, String eStructuralFeatureName) { final Element element = new Element("xsd:attribute"); element.addAttribute(new Attribute("name", convertToXmlName(name))); element.addAttribute(new Attribute("type", type)); if (!name.equals(eStructuralFeatureName)) { addAppInfoElement(element, eStructuralFeatureName); } return element; } private static void addAppInfoElement(final Element element, String eStructuralFeatureName) { final Element annotationElement = new Element("xsd:annotation"); element.addElement(annotationElement); final Element appInfoElement = new Element("xsd:appinfo"); appInfoElement.addAttribute(new Attribute("source", ESTRUCTURAL_FEATURE_SOURCE_NAME)); appInfoElement.setText(eStructuralFeatureName); annotationElement.addElement(appInfoElement); } /** * Creates an XML Schema complex type. (<xsd:complexType>) */ private Element createSchemaComplexType(String name) { final Element element = new Element("xsd:complexType"); element.addAttribute(new Attribute("name", name)); return element; } /** * Tests whether an EModelElement is unsupported. * */ protected static boolean isUnsupported(EModelElement eModelElement) { return (eModelElement.getEAnnotation(UNSUPPORTED_SOURCE) != null); } /** Add minOccurs and maxOccurs */ private void addZeroUnbounded(Element elem) { addMinMaxOccurs(elem, "0", "unbounded"); } /** Add minOccurs and maxOccurs */ private void addMinMaxOccurs(Element elem, String min, String max) { elem.addAttribute("minOccurs", min).addAttribute("maxOccurs", max); } /** * Converts mixed-case names to XML names. * <p> * Example: "generatedValue" -> "generated-value". * * @param name * @return */ protected String convertToXmlName(String name) { StringBuffer sb = new StringBuffer(); for (int i = 0, n = name.length(); i < n; i++) { char ch = name.charAt(i); if (Character.isUpperCase(ch)) { if (i > 0) { sb.append('-'); } ch = Character.toLowerCase(ch); } sb.append(ch); } return sb.toString(); } /** EFeature comparator */ private class ENamedElementComparator implements Comparator<ENamedElement> { /** Compare features */ public int compare(ENamedElement e1, ENamedElement e2) { return e1.getName().compareTo(e2.getName()); } } /** * @return the annotationEPackages */ public EPackage[] getAnnotationEPackages() { return annotationEPackages; } /** * @param annotationEPackages * the annotationEPackages to set */ public void setAnnotationEPackages(EPackage[] annotationEPackages) { this.annotationEPackages = annotationEPackages; } /** * @return the modelEPackage */ public EPackage getModelEPackage() { return modelEPackage; } /** * @param modelEPackage * the modelEPackage to set */ public void setModelEPackage(EPackage modelEPackage) { this.modelEPackage = modelEPackage; } /** * @return the nameSpace */ public String getNameSpace() { return nameSpace; } /** * @param nameSpace * the nameSpace to set */ public void setNameSpace(String nameSpace) { this.nameSpace = nameSpace; } }